/*
 *  (c) Copyright 2003, 2004, 2005 2006, 2007, 2008, 2009 Hewlett-Packard Development Company, LP
 *  All rights reserved.
 *
 *
 */

//=======================================================================
// Package
package com.hp.hpl.jena.db.impl;

//=======================================================================
// Imports
import java.sql.*;
import java.util.*;
import com.hp.hpl.jena.util.iterator.*;
import com.hp.hpl.jena.shared.*;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

//=======================================================================
/**
* Iterates over an SQL result set returning each row as an ArrayList of
* objects. The returned array is shared at each iteration so calling next() or even hasNext()
* changes the array contents. When the iterator terminates the resources
* are cleaned up and the underlying SQL PreparedStatement is returned to
* the SQLCache pool from whence it came.
* 
* <p>Override the extractRow, getRow, and remove methods in subclasses 
* to return an object collection derived from the row contents instead 
* of the raw row contents.
*
* @author <a href="mailto:der@hplb.hpl.hp.com">Dave Reynolds</a>
* @version $Revision: 1.1 $ on $Date: 2009/06/29 08:55:37 $
*/

public abstract class ResultSetIterator<T> implements ExtendedIterator<T>{
    // T is the type of the  
    
    /** The ResultSet being iterated over */
    protected ResultSet m_resultSet;

    /** The originating SQLcache to return the statement to, can be null */
    protected SQLCache m_sqlCache;

    /** The source Statement to be cleaned up when the iterator finishes - return it to cache or close it if no cache */
    protected PreparedStatement m_statement;

    /** If true, clean/close the prepared statement when iterator is closed */
    protected boolean m_statementClean = true;
  
    /** The name of the original operation that lead to this statement, can be null if SQLCache is null */
    protected String m_opname;

    /** The contents of the current row */
    protected T m_row;

    /** The number of columns in this result set */
    protected int m_nCols;

    /** Flag that the iteration has finished */
    protected boolean m_finished = false;

    /** Flag if we have prefeteched the next row but not yet returned it */
    protected boolean m_prefetched = false;

    private static Logger logger = LoggerFactory.getLogger( ResultSetIterator.class );
    /**
     * Create an empty iterator.
     * Needs to be initialized by reset
     * before it can be accessed. Useful to allow generic functions like
     * {@link SQLCache#runSQLQuery runSQLQuery}
     * to return different iterator types to the client.
     */
    public ResultSetIterator() {
        m_finished = true;      // Prevent reading until reset
    }

    /**
     * Iterate over the results of a PreparedStatement generated by an SQLCache
     * @param resultSet the result set being iterated over
     * @param sourceStatement The source Statement to be cleaned up when the iterator finishes - return it to cache or close it if no cache
     * @param cache The originating SQLcache to return the statement to, can be null
     * @param opname The name of the original operation that lead to this statement, can be null if SQLCache is null
     */
    public ResultSetIterator(ResultSet resultSet, PreparedStatement sourceStatement, SQLCache cache, String opname) {
        m_resultSet = resultSet;
        m_sqlCache = cache;
        m_statement = sourceStatement;
        m_opname = opname;
    }

    /**
     * Iterate over the results of a PreparedStatement, close the statement when finished.
     * @param resultSet the result set being iterated over
     * @param sourceStatement The source Statement to be closed when the iterator finishes
     */
    public ResultSetIterator(ResultSet resultSet, PreparedStatement sourceStatement) {
        m_resultSet = resultSet;
        m_statement = sourceStatement;
    }

    /**
     * Reset an existing iterator to scan a new result set.
     * @param resultSet the result set being iterated over
     * @param sourceStatement The source Statement to be cleaned up when the iterator finishes - return it to cache or close it if no cache
     * @param cache The originating SQLcache to return the statement to, can be null
     * @param opname The name of the original operation that lead to this statement, can be null if SQLCache is null
     */
    public void reset(ResultSet resultSet, PreparedStatement sourceStatement, SQLCache cache, String opname) {
        m_resultSet = resultSet;
        m_sqlCache = cache;
        m_statement = sourceStatement;
        m_opname = opname;
        m_finished = false;
        m_prefetched = false;
        m_row = null;
        m_statementClean = true;
    }
    
    /**
     * Reset an existing iterator to scan a new result set.
     * @param resultSet the result set being iterated over
     * @param sourceStatement The source Statement to be cleaned up when the iterator finishes - return it to cache or close it if no cache
     * note: the sourceStatement is not closed or returned when the iterator is closed.
     */
    public void reset(ResultSet resultSet, PreparedStatement sourceStatement) {
        m_resultSet = resultSet;
        m_sqlCache = null;
        m_statement = sourceStatement;
        m_opname = null;
        m_finished = false;
        m_prefetched = false;
        m_row = null;
        m_statementClean = false;
    }

    /**
     * Test if there is a next result to return
     */
    public boolean hasNext() {
        if (!m_finished && !m_prefetched) moveForward();
        return !m_finished;
    }
    
    public T removeNext()
        { cantRemove(); return null; }

    /**
     * Return the current row
     */
    public T next() {
        if (!m_finished && !m_prefetched) moveForward();
        m_prefetched = false;
        if (m_finished) {
            throw new NoSuchElementException();
        }
        return getRow() ;
    }

    /**
     * Delete the current row entry
     */
    public void remove() {
        cantRemove();
    }
    
    protected void cantRemove() {
        throw new UnsupportedOperationException("ResultSetIterator can't remove database rows");
    }

    /**
     * More forward one row. Sets the m_finished flag if there is no more to fetch
     */
    protected void moveForward() {
        try {
            if (!m_finished && m_resultSet.next()) {
                extractRow();
                m_prefetched = true;
            } else {
                close();
            }
        } catch (Exception e) {
            //  TODO do we need this catch at all?
            logger.warn("Problem in iterator over db result set, op = " + m_opname, e);
            // Added by kers for debugging
            throw new JenaException( e );
        }
    }

    /**
     * Extract the current row
     * Override in subclasses.
     */
    protected abstract void extractRow() throws Exception ; 
//    {
//        if (m_row == null) {
//            m_nCols = m_resultSet.getMetaData().getColumnCount();
//            m_row = new ArrayList<Object>(m_nCols);
//            for (int i = 0; i < m_nCols; i++) m_row.add(null);
//        }
//        for (int i = 0; i < m_nCols; i++) {
//            m_row.set(i, m_resultSet.getObject(i+1));
//        }
//    }

    /**
     * Return the current row,should have already been extracted.
     * Override in subclasses.
     */
    protected T getRow() {
        return m_row;
    }

    /**
     * Clean up the allocated resources - result set and statement.
     * If we know of an SQLCache return the statement there, otherwise close it.
     */
    public void close() {
        if (!m_finished) {
            if (m_resultSet != null) {
                try {
                    m_resultSet.close();
                    m_resultSet = null;
                } catch (SQLException e) {
                    logger.warn("Error while finalizing result set iterator", e);
                }
            }
            if (m_statementClean) {
            if (m_sqlCache != null && m_opname != null) {
                m_sqlCache.returnPreparedSQLStatement(m_statement);
            } else {
                try {
                    m_statement.close();
                } catch (SQLException e) {
                    logger.warn("Error while finalizing result set iterator", e);
                }
            }
            }
        }
        m_finished = true;
    }

    /**
     * Get a singleton result (single column from single row) and close the iterator.
     * This may be too specialized but seems to come up a lot - rethink.
     */
    public Object getSingleton() throws SQLException {
        List<?> row = (List<?>)next();
        close();
        return row.get(0);
    }

    /**
     * Clean up the database cursor. Noramlly the client should read to the end
     * or explicity close but....
     */
    @Override
    protected void finalize() throws SQLException {
        if (!m_finished && m_resultSet != null) close();
    }

	/**
         return a new iterator which delivers all the elements of this iterator and
         then all the elements of the other iterator. Does not copy either iterator;
         they are consumed as the result iterator is consumed.
     */
	public <X extends T> ExtendedIterator<T> andThen( Iterator<X> other ) {
		return NiceIterator.andThen(this, other);
	}

    public Set<T> toSet() {
        return NiceIterator.asSet( this ); 
        }
    
    public List<T> toList() {
        return NiceIterator.asList( this ); 
        }
    
	/* (non-Javadoc)
	 * @see com.hp.hpl.jena.util.iterator.ExtendedIterator#filterKeep(com.hp.hpl.jena.util.iterator.Filter)
	 */
	public ExtendedIterator<T> filterKeep(Filter<T> f) {
		return new FilterIterator<T>( f, this );
	}

	/* (non-Javadoc)
	 * @see com.hp.hpl.jena.util.iterator.ExtendedIterator#filterDrop(com.hp.hpl.jena.util.iterator.Filter)
	 */
	public ExtendedIterator<T> filterDrop(final Filter<T> f)
    {
        Filter<T> notF = new Filter<T>() {
            @Override
            public boolean accept(T x)
            {
                return !f.accept(x) ;
            }
        } ;
        return new FilterIterator<T>(notF, this) ;
    }

	/* (non-Javadoc)
	 * @see com.hp.hpl.jena.util.iterator.ExtendedIterator#mapWith(com.hp.hpl.jena.util.iterator.Map1)
	 */
	public <X> ExtendedIterator<X> mapWith(Map1<T,X> map1) {
		return new Map1Iterator<T,X>( map1, this ); 
	}

} // End class

/*
 *  (c) Copyright 2003, 2004, 2005, 2006, 2007, 2008, 2009 Hewlett-Packard Development Company, LP
 *  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.

 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
